/*
 * Created on 14-Nov-2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.mtdownload.download;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPCommand;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.mtdownload.utils.PreferencesManager;

/**
 * A thread that does the downloading :)
 * @author sergiu
 */
public class DownloadMasterThread extends DownloadThread{

	/**
	 * Comment for <code>serialVersionUID</code>
	 */
	private static final long serialVersionUID = 3834310631099939123L;
	private transient Timer speedMeasurementTimer=null;	
	private boolean allFinished=false;
	private int threadCounter=0;
	private TaskInfoUpdater taskInfoUpdater=null;
	
	public DownloadMasterThread(DownloadTask task){
		super(task,0);
		//setThreads(new Vector<DownloadThread>());
		//setBytesCounter( new BytesCounter());
	}
	
	public void run(){
		threadCounter=0;
		super.run();
		if(allFinished){
			if(speedMeasurementTimer!=null){
				speedMeasurementTimer.cancel();
			}
			infoStream.println("Download finished");
			taskInfoUpdater.updateTaskInfo(task,counter,downloadFileLength);
			manager.notifyListeners(task);
		}
	}
	
	private synchronized long  getDownloadFileLength(URLConnection con){
		long length=-1;
		try {			
			String strLength=con.getHeaderField("content-length");
			if(strLength==null){
				return -1;
			}
			length=Long.parseLong(strLength);			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return length;
	}
	
	protected void startFTPDownload(final DownloadTask task,URL url){
		infoStream.println("Downloading "+url);
		task.setStatus("Connecting");
		manager.notifyListeners(task);
		FTPClient ftp=new FTPClient();
		//ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
		try{
			int port=(url.getPort()!=-1?url.getPort():url.getDefaultPort());
			ftp.connect(url.getHost(),port);
			fromClientStream.println("connected to "+url.getHost());
			fromServerStream.println(ftp.getReplyString());
			int reply=ftp.getReplyCode();
			if(!FTPReply.isPositiveCompletion(reply)){
				ftp.disconnect();
				errorStream.println("ftp server refused connection");
				return;
			}
		}catch(IOException exc){
			if(ftp.isConnected()){
				try{
					ftp.disconnect();
				}catch(IOException ignored){/*ignored*/}
				
			}
			errorStream.println("could not connect to server. aborting");
			exc.printStackTrace();
			return;
		}
		String username=task.getUsername();
		String password=task.getPassword();
		if(username==null){
			//set the username to anonymous, and an e-mail adress as the password
			username="anonymous";
			password=PreferencesManager.getAnonymousFTPPassword();
		}	
		try{
			if(!ftp.login(username,password)){
				ftp.logout();
				errorStream.println("Error logging in. Aborting.");
				return;
			}			
			fromServerStream.println("remote system is :" + ftp.getSystemName());
			int reply=ftp.sendCommand(FTPCommand.REST,"100");//check if the server supports resuming
			fromServerStream.println("REST command reply "+reply);
			boolean acceptResume=FTPReply.isPositiveIntermediate(reply);
			if(acceptResume){				
				ftp.sendCommand(FTPCommand.REST,new Long(getCurrentPosition()).toString());
				fromServerStream.println("REST command reply "+reply);
			}else{				
				ftp.sendCommand(FTPCommand.REST,"0");			
				fromServerStream.println("REST command reply "+reply);
			}
			String ftpFileType=PreferencesManager.getFTPTransferType();
			if(ftpFileType.equals(PreferencesManager.FTP_TYPE_ASCII)){
				ftp.setFileType(FTP.ASCII_FILE_TYPE);		
			}else{
				ftp.setFileType(FTP.BINARY_FILE_TYPE);
			}
			
			ftp.enterLocalPassiveMode();			
			FTPFile[] files=ftp.listFiles(url.getPath());
			if(files==null||files.length==0){
				errorStream.println("no files found. aborting.");
				return;
			}
			//we should only have one file			
			downloadFileLength=files[0].getSize();
			task.setFileLength(downloadFileLength);			
			String destination=task.getDestinationFileName();
			infoStream.println("destination: "+destination);
			destinationFile=new File(destination);
			
			task.setStatus("Creating file");
			manager.notifyListeners(task);
			RandomAccessFile out=createFile(destinationFile,downloadFileLength);
			InputStream in=ftp.retrieveFileStream(url.getPath());
			task.setStarted(true);
			task.setStatus("Downloading");
			manager.notifyListeners(task);	        
	        generateThreads(task, acceptResume, destination, out, in);
			
		}catch(Exception exc){
			exc.printStackTrace();
		}finally{
			if(ftp.isConnected()){
				try{
					ftp.disconnect();
				}catch(IOException ignored){/*ignored*/}				
			}
		}
	}

	/**
	 * This method spawns the threads
	 */
	private void generateThreads(final DownloadTask task, boolean acceptResume, String destination, RandomAccessFile out, InputStream in) throws IOException, Exception {
		speedMeasurementTimer=null;
		speedMeasurementTimer=new Timer("SpeedMeasurement",true);
		setName("Master Thread "+ destination);
		int threadNum=task.getThreadNum();
		if(counter==null){
			setBytesCounter(new BytesCounter(task));
		}
		setCancelled(false);
		if(threads==null && threadNum>1 && acceptResume){
			setThreads(new Vector<DownloadThread>());
			threads.add(this);
			setIndex(0);
			if(threadNum>1 && acceptResume){
				//ok, we want multiple threads ... (and we are allowed to have those)
				long bytesPerThread=downloadFileLength/threadNum;
				for(int i=1;i<threadNum;i++){
					DownloadThread t=new DownloadThread(getTask(),bytesPerThread*i);
					t.setEndPosition(bytesPerThread*(i-1));				
					t.setName("Thread "+i);
					t.setMasterThread(this);
					threads.add(t);
					t.setThreads(threads);
					t.setBytesCounter(counter);
					t.setIndex(i);
					new Thread(t).start();
				}
				getTask().setThreadsSpeed(new long[threadNum]);				
			}
		}else{
			if(threads!=null){
				for(DownloadThread thread:threads){
					if(thread.equals(this)) continue;
					thread.setCancelled(false);				
					new Thread(thread).start();
				}
				getTask().setAllThreadsSpawned(true);
			}else{
				setThreads(new Vector<DownloadThread>());
				threads.add(this);
				getTask().setThreadsSpeed(new long[1]);
				setIndex(0);
			}
		}
		getTask().setAllThreadsSpawned(true);
		if(taskInfoUpdater==null){
			taskInfoUpdater=new TaskInfoUpdater();	
		}
		speedMeasurementTimer.scheduleAtFixedRate(new TimerTask(){
				public void run(){
					taskInfoUpdater.updateTaskInfo(task,counter,downloadFileLength);
					manager.notifyListeners(task);
				}
			},0,1000);
		out.seek(getCurrentPosition());
		downloadBytes(out,in);
		out.close();
		threadCompleted(this);
	}	

	protected void startHTTPDownload(final DownloadTask task,URL url) throws Exception{
		infoStream.println("Downloading "+url);
		task.setStatus("Connecting");
		manager.notifyListeners(task);

		if(task.getUsername()!=null && task.getUsername().length()>0){
			//TODO This needs testing. I'm really not that sure if this works		
			//It should though
			url=new URL(url.getProtocol()+"://"+task.getUsername()+":"+task.getPassword()+"@"+url.getPath());
		}		
		HttpURLConnection  connection=getHTTPConnection(url);
		
		connection.addRequestProperty("User-Agent","MTDownload 0.1");
		if(task.getReferer()!=null && task.getReferer().length()>0){
			connection.addRequestProperty("Referer",task.getReferer());
		}
		connection.connect();
		int responseCode=connection.getResponseCode();
		if(responseCode==HttpURLConnection.HTTP_MOVED_PERM || 
			responseCode==HttpURLConnection.HTTP_MOVED_TEMP	){
			infoStream.println("The file moved out. Lets go find it.");
			String newURL=connection.getHeaderField("Location");
			task.setUrl(newURL);
			fromServerStream.println(newURL);
			//run this again, with the new URL
			run();
			return;
		}
		if(responseCode!=HttpURLConnection.HTTP_PARTIAL&& 
		    responseCode!=HttpURLConnection.HTTP_ACCEPTED && 
			responseCode!=HttpURLConnection.HTTP_OK ){
			errorStream.println("Error connecting to server");
			errorStream.println("Reponse message: code="+responseCode+", message:"+connection.getResponseMessage());
			connection.disconnect();
			task.setStatus("Error");
			manager.notifyListeners(task);
			return;
		}
		
		downloadFileLength=getDownloadFileLength(connection);
		task.setFileLength(downloadFileLength);
		boolean acceptRange=connection.getHeaderField("Accept-Ranges")!=null;
		fromServerStream.println("accept range:"+acceptRange);
		connection.disconnect();//disconnect for now
		connection=null;
		//we have to create the file
		//determine how many threads should we start...etc.
		
		String destination=task.getDestinationFileName();
		infoStream.println("destination: "+destination);
		destinationFile=new File(destination);
		
		task.setStatus("Creating file");
		manager.notifyListeners(task);
		RandomAccessFile out=createFile(destinationFile,downloadFileLength);
		
		task.setStarted(true);
		task.setStatus("Downloading");
		manager.notifyListeners(task);
		
		connection=getHTTPConnection(url);        
		connection.addRequestProperty("Range","bytes="+getCurrentPosition()+"-");//we're the master thread, we start from the start. whatever that might be
        Map<String,List<String>> hmap=connection.getHeaderFields();
        Set<String> hset=hmap.keySet();
        Iterator<String> it=hset.iterator();
        while(it.hasNext()){
                String key=it.next();
                fromServerStream.println(key+" "+hmap.get(key));
        }
		connection.connect();
        InputStream in=connection.getInputStream();
		generateThreads(task, acceptRange, destination, out, in);		
	}
	
	public void threadCompleted(DownloadThread t) throws Exception{
		threadCounter++;
		t.setCompleted(true);
		if(threadCounter>=threads.size()){
			allFinished=true;
			if(downloadFileLength<=counter.getCompletedBytes()){
				task.setComplete(true);
				task.setStatus("Complete");
				manager.notifyListeners(task);
				System.out.println("all finished");
			}else{
				task.setComplete(false);
				task.setStatus("Paused");
				manager.notifyListeners(task);
				System.out.println("all paused");				
			}
		}else{
			allFinished=false;
			//here we sould check if we have any threads that still are downloading, and if we do ... we might 
			//get in the middle
		}
		if(allFinished){
			if(speedMeasurementTimer!=null){
				speedMeasurementTimer.cancel();
			}
			//should be always 100%
			task.setPercentComplete((double)counter.getCompletedBytes()/(double)downloadFileLength);			
			manager.notifyListeners(task);
			infoStream.println("Download finished");					
		}		
	}
}
